/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package treegen;

import java.util.ArrayList;
import java.util.Random;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.geometry.Point2D;
import javafx.geometry.Point3D;
import javafx.scene.DepthTest;
import javafx.scene.Group;
import javafx.scene.image.Image;
import javafx.scene.paint.Color;
import javafx.scene.paint.ImagePattern;
import javafx.scene.shape.Polygon;
import javafx.scene.shape.Rectangle;

/**
 * Перечисление для описания формы ствола
 * @author Елена
 */
enum TrunkShape {
    vertical, sloping, curved
};

/**
 * Перечисление для описания текстуры ствола
 * @author Елена
 */
enum TrunkTexture {
    smooth, plastic, smallCrack, largeCrack
};

/**
 * Класс ствола. Хранит все необходимые параметры и занимается собственной отрисовкой.
 * @author Сергей
 */
public class Trunk {
    /** Ширина*/
    double width;
    /**Высота*/
    double height;
    /** Форма*/
    int shape;
    /** Количество свтолов*/
    int truncCount;
    /** Высота начала ветвей*/
    double hStartBranches;
    /** Количество разветвлений*/
    int divisionCount;
    /** Высота начала разветвлений*/
    double hStartDivision;
    /** Текстура*/
    TrunkTexture texture;
    /** Тип ствола в строковой форме */
    String strShape = "";
    /** Текстура ствола в строковой форме */
    String strTexture = "";
    /** панель для ввода парметров*/
    TrunkPanel panel;
    /** дерево*/
    public Tree tree;
    /** Флаг, что парамтеры установлены*/
    boolean isSet;
    /** Разветвления*/
    ArrayList<Trunk> divisions = new ArrayList<>();
    /** Начальная точка*/
    Point2D P0;
    /** Точка середины*/
    Point2D P1;
    /** Конечная точка*/
    Point2D P2;

    /** 
     * Конструтор ствола
     * @param trunkPanel панель для ввода параметра ствола
     * @param tree дерево
     */
    public Trunk(TrunkPanel trunkPanel, Tree tree) {

        panel = trunkPanel;
        this.tree = tree;
        isSet = false;
    }

    /**
     * Получить ширину ствола
     * @return ширина ствола
     */
    public double getWidth() {
        return width;
    }
    /**
     * Получить высоту ствола
     * @return высота ствола
     */
    public double getHeight() {
        return height;
    }

    /**
     * Получить форму ствола
     * @return форма ствла
     */
    public int getShape() {
        return shape;
    }

    /**
     * Получить количество стволов
     * @return количество стволов
     */
    public int getTruncCount() {
        return truncCount;
    }

    /**
     * Получить выстоту начала ветвей
     * @return выстота начала ветвей
     */
    public double getHStartBranches() {
        return hStartBranches;
    }

    /**
     * Получить количество разветвлений
     * @return количество разветвлений
     */
    public int getDivisionCount() {
        return divisionCount;
    }

    /**
     * Получить высоту начала разветвлений
     * @return высота начала разветвления
     */
    public double getHStartDivision() {
        return hStartDivision;
    }

    /**
     * Получить текстуру
     * @return текстура
     */
    public TrunkTexture getTexture() {
        return texture;
    }

    /**
     * Нарисовать разветвление
     * @param x координата x
     * @param y координата y
     * @param image изображение для заливки
     * @param divWidth ширина разветвления
     * @param divHeight высота разветвления
     * @param realHeight реальная высота ствола без разветвлений
     * @return группа, представляющая собой изображения с разветвлениями
     */
    public Group paintDivision(int x, int y, Image image, double divWidth, double divHeight, double realHeight) {
        Polygon polygon;
        Trunk divTrunk;
        Group divGroup = new Group();
        for (int i = 0; i < divisionCount; i++) {
            if (i == 0) {
                polygon = new Polygon();
                polygon.getPoints().addAll(new Double[]{
                    (double) (x + width / 2 - divWidth), (double) y - realHeight,
                    (double) (x + width / 2), (double) y - realHeight,
                    (double) (x + width / 2 + divWidth / 2), (double) (y - realHeight - divHeight),
                    (double) (x + width / 2 - divWidth / 2), (double) (y - realHeight - divHeight)});
                polygon.setFill(new ImagePattern(image, 0, 0, 100, 100, false));

                divGroup.getChildren().add(polygon);
                divTrunk = new Trunk(panel, tree);
                divTrunk.height = divHeight;
                divTrunk.width = divWidth;
                divTrunk.shape = 1;
                divTrunk.texture = texture;
                divTrunk.hStartBranches = hStartBranches - hStartDivision;
                divTrunk.P0 = new Point2D(x + width/2 - divWidth/2, y - realHeight);
                divTrunk.P1 = new Point2D(x + width/2, y - realHeight - divHeight);
                divisions.add(divTrunk);
            } else if (i == 1) {
                polygon = new Polygon();
                polygon.getPoints().addAll(new Double[]{
                    (double) (x - width / 2 + divWidth), (double) y - realHeight,
                    (double) (x - width / 2), (double) y - realHeight,
                    (double) (x - width / 2 - divWidth / 2), (double) (y - realHeight - divHeight),
                    (double) (x - width / 2 + divWidth / 2), (double) (y - realHeight - divHeight)});
                polygon.setFill(new ImagePattern(image, 0, 0, 100, 100, false));
                divGroup.getChildren().add(polygon);
                divTrunk = new Trunk(panel, tree);
                divTrunk.height = divHeight;
                divTrunk.width = divWidth;
                divTrunk.shape = 1;
                divTrunk.texture = texture;
                divTrunk.hStartBranches = hStartBranches - hStartDivision;
                divTrunk.P0 = new Point2D(x - width/2 + divWidth/2, y - realHeight);
                divTrunk.P1 = new Point2D(x - width/2, y - realHeight - divHeight);
                divisions.add(divTrunk);
            } else if (i == 2) {
                Rectangle rect = new Rectangle(x - divWidth / 2, y - realHeight - divHeight, divWidth, divHeight);
                rect.setFill(new ImagePattern(image, 0, 0, 100, 100, false));
                divGroup.getChildren().add(rect);
                divTrunk = new Trunk(panel, tree);
                divTrunk.height = divHeight;
                divTrunk.width = divWidth;
                divTrunk.shape = 0;
                divTrunk.hStartBranches = hStartBranches - hStartDivision;
                divTrunk.P0 = new Point2D(x, y - realHeight);
                divTrunk.P1 = new Point2D(x, y - realHeight - divHeight);
                divTrunk.texture = texture;
                divisions.add(divTrunk);
            } else if (i == 3) {
                polygon = new Polygon();
                polygon.getPoints().addAll(new Double[]{
                    (double) (x + width / 2 - 2 * divWidth), (double) y - realHeight + divWidth,
                    (double) (x + width / 2 - divWidth), (double) y - realHeight + divWidth,
                    (double) (x + width / 2 + divWidth / 2 + divWidth), (double) (y - realHeight - divHeight + divWidth),
                    (double) (x + width / 2 - divWidth / 2 + divWidth), (double) (y - realHeight - divHeight + divWidth)});
                polygon.setFill(new ImagePattern(image, 0, 0, 100, 100, false));
                double d = polygon.translateZProperty().getValue();

                divGroup.getChildren().add(polygon);
                divTrunk = new Trunk(panel, tree);
                divTrunk.height = divHeight;
                divTrunk.width = divWidth;
                divTrunk.shape = 1;
                polygon.translateZProperty().bind(new SimpleDoubleProperty(-100));
                divTrunk.hStartBranches = hStartBranches - hStartDivision;
                divTrunk.P0 = new Point2D(x + width / 2 - 1.5 * divWidth, y - realHeight+ divWidth);
                divTrunk.P1 = new Point2D(x + width / 2 + divWidth, y - realHeight - divHeight + divWidth);
                divTrunk.texture = texture;
                divisions.add(divTrunk);
            } else if (i == 4) {
                polygon = new Polygon();
                polygon.getPoints().addAll(new Double[]{
                    (double) (x - width / 2 + 2 * divWidth), (double) y - realHeight + divWidth,
                    (double) (x - width / 2 + divWidth), (double) y - realHeight + divWidth,
                    (double) (x - width / 2 - divWidth / 2 - divWidth), (double) (y - realHeight - divHeight + divWidth),
                    (double) (x - width / 2 + divWidth / 2 - divWidth), (double) (y - realHeight - divHeight + divWidth)});
                polygon.setFill(new ImagePattern(image, 0, 0, 100, 100, false));
                divGroup.getChildren().add(polygon);
                divGroup.setDepthTest(DepthTest.ENABLE);
                polygon.translateZProperty().bind(new SimpleDoubleProperty(-40));
                divTrunk = new Trunk(panel, tree);
                divTrunk.height = divHeight;
                divTrunk.width = divWidth;
                divTrunk.shape = 1;
                divTrunk.hStartBranches = hStartBranches - hStartDivision;
                divTrunk.P0 = new Point2D(x + width / 2 + 1.5 * divWidth, y - realHeight+ divWidth);
                divTrunk.P1 = new Point2D(x - width / 2 - divWidth, y - realHeight - divHeight + divWidth);
                divTrunk.texture = texture;
                divisions.add(divTrunk);
            }
        }
        return divGroup;
    }

    /**
     * Нарисовать ствол
     * @param x координата x
     * @param y координата y
     * @return группа с изображением ствола
     */
    public Group paint(int x, int y) {
        Group result = new Group();
        result.setDepthTest(DepthTest.ENABLE);
        Random rand = new Random();
        Rectangle trunkRect;
        Polygon polygon;
        Image image;
        double angleCurve = -90.0;
        double realHeight;
        double divWidth = 0;
        double divHeight = 0;
        double divAngle = 0;
        double roundAngle = 0;
        int startX;
        int startY;
        result.setDepthTest(DepthTest.ENABLE);
        if (divisionCount == 0) {
            realHeight = height;
        } else {
            realHeight = hStartDivision;
            divHeight = height - realHeight;
            divWidth = width / divisionCount;
            divAngle = 360 / divisionCount;
            if (divAngle == 90 || divAngle == 180) {
                divAngle -= 20;
            }
        }
        Center c;
        // Выбираем структуру коры
        if (texture == TrunkTexture.smooth) {
            image = new Image(BranchLevel.class.getResourceAsStream("pictures/smooth1.png"));
        } else if (texture == TrunkTexture.plastic) {
            image = new Image(BranchLevel.class.getResourceAsStream("pictures/plastic.png"));
        } else if (texture == TrunkTexture.smallCrack) {
            image = new Image(BranchLevel.class.getResourceAsStream("pictures/small.png"));
        } else {
            image = new Image(BranchLevel.class.getResourceAsStream("pictures/large.png"));
        }
        double angle = (double) (89 - rand.nextInt(20));
        if (shape == 0) {
            if (divisionCount != 0) {
                result.getChildren().add(paintDivision(x, y, image, divWidth, divHeight, realHeight));
            }
            trunkRect = new Rectangle(x - width / 2, y - realHeight, width, realHeight);
            trunkRect.setFill(new ImagePattern(image, 0, 0, 100, 100, false));
            result.getChildren().add(trunkRect);
            P0 = new Point2D(x, y);
            P1 = new Point2D(x, y - realHeight);
            //result.setRotationAxis(new Point3D(1, 0, 0));
            //result.setRotate(180);
            //result.setTranslateY(-height/2 - width);
        } else if (shape == 2) {
            Group bGr = new Group();
            Branch branch = new Branch(1, 1);
            branch.length = realHeight;
            branch.width = width;
            double xTemp = Math.tan(Math.toRadians(30)) * realHeight / 2 + width;
            bGr.getChildren().add(branch.paintCurveBranch((int) (x - realHeight / 2), (int) (y + realHeight / 2), image, false));
            P0 = branch.P0;
            P1 = branch.P1;
            P2 = branch.P2;
            Rectangle bottom = new Rectangle((x - realHeight / 2), y - width + realHeight / 2, width, width);
            bottom.setFill(Color.WHITE);
            Rectangle top = new Rectangle((x + realHeight / 2 - width), y - width + realHeight / 2, width, width);
            top.setFill(Color.WHITE);
            bGr.getChildren().add(bottom);
            bGr.getChildren().add(top);
            bGr.setRotationAxis(new Point3D(0, 0, 1));

            bGr.setRotate(angleCurve);
            //bGr.setTranslateY(-realHeight);
            result.getChildren().add(bGr);
            c = new Center(bGr);
            double xc = c.centerXProperty().getValue();
            double yc = c.centerYProperty().getValue();
            result.getChildren().add(paintDivision((int) xc + 1, (int) (yc + width + realHeight / 2 + 1), image, divWidth, divHeight, realHeight));
        } else {
            polygon = new Polygon();
            polygon.getPoints().addAll(new Double[]{
                (double) (x - width / 2), (double) y,
                (double) (x + width / 2), (double) y,
                (double) (x + width / 2 + realHeight * Math.cos(Math.toRadians(angle))), (double) (y - realHeight * Math.sin(Math.toRadians(angle))),
                (double) (x - width / 2 + realHeight * Math.cos(Math.toRadians(angle))), (double) (y - realHeight * Math.sin(Math.toRadians(angle)))});
            polygon.setFill(new ImagePattern(image, 0, 0, 100, 100, false));
            result.getChildren().add(polygon);
            result.getChildren().add(paintDivision((int) (x + realHeight * Math.cos(Math.toRadians(angle))), (int) (y - realHeight * Math.sin(Math.toRadians(angle)) + realHeight + 1), image, divWidth, divHeight, realHeight));
            P0 = new Point2D(x, y);
            P1 = new Point2D(x + width / 2 + realHeight * Math.cos(Math.toRadians(angle)), y - realHeight * Math.sin(Math.toRadians(angle)));
        }
        for (int i = 0; i < tree.branches.size(); i++) {
            if (i < tree.branches.size() - 1) {
                tree.branches.get(i).paintLevel(result, tree, this, tree.branches.get(i + 1));
            } else {
                tree.branches.get(i).paintLevel(result, tree, this, null);
            }
        }
        return result;
    }

    /** 
     * Получить координату x для прямого ствола
     * @param y координата Y
     * @return координата x
     */
    public double getX(double y) {
        double alpha = Math.abs(P1.getX() - P0.getX()) / height;
        alpha = Math.acos(alpha);
        double k = Math.tan(alpha);
        double b = P0.getY() - k * P0.getX();
        return (y - b) / k;
    }

    /**
     * Получить точку на кривой
     * @param t параметр кривых Безье
     * @return точка на кривой
     */
    public Point2D getPointOnCurve(double t) {
        return new Point2D(Math.pow((1 - t), 2.0) * P0.getX() + 2 * t * (1 - t) * P1.getX() + t * t * P2.getX(), Math.pow((1 - t), 2.0) * P0.getY() + 2 * t * (1 - t) * P1.getY() + t * t * P2.getY());
    }
}
